home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
090
/
bytjl86a.arc
/
SPREAD.ARC
/
EVALUAT.MOD
< prev
next >
Wrap
Text File
|
1985-07-12
|
10KB
|
392 lines
IMPLEMENTATION MODULE Evaluator;
(* Evaluator for the spreadsheet.
*)
(* grammar for formulas: ( {} means "zero or more" )
<expr> ::= <valexpr> | <valexpr> <relop> <valexpr> |
IF <expr> , <expr> , <expr>
<valexpr> ::= <term> { <addop> <term> }
<term> ::= <factor>{ <mulop> <factor> }
<factor> ::= real | <cellref> | - <factor> | ( <expr> )
<cellref> ::= [ <refexpr> , <refexpr> ]
<refexpr> ::= <addop> real | real
<relop> ::= < | > | = | <> | >= | <=
<addop> ::= + | -
<mulop> ::= * | /
1 = TRUE, 0 = FALSE.
*)
FROM Misc IMPORT fatal, assert;
FROM Spreadsheet IMPORT maxRow, maxCol, Status, status, getValue;
FROM StringStuff IMPORT string40, string160, stringCopy, findChar;
FROM CharStuff IMPORT isDigit, isWhite;
FROM RealConversions IMPORT StrToReal, RealProcResponses, RealConversionRes;
FROM Formula IMPORT formula;
IMPORT Formula;
FROM DisplayHandler IMPORT message;
FROM StringOps IMPORT Concat;
TYPE
relOpType = (Less, Greater, Equal, LessEqual, GreaterEqual, NotEqual);
VAR curRow, curCol:CARDINAL;
PROCEDURE evaluateFormula(f:formula; row, col:CARDINAL; VAR v:REAL;
VAR s:Status);
VAR str:string160;
BEGIN
Formula.toString(f, str);
evaluateString(str, row, col, v, s);
END evaluateFormula;
PROCEDURE evaluateString(str:ARRAY OF CHAR; row, col:CARDINAL; VAR v:REAL;
VAR s:Status);
VAR pos:CARDINAL;
BEGIN
pos := 0;
curRow := row;
curCol := col;
expr(str, pos, v, s, TRUE);
END evaluateString;
(* <expr> ::= <valexpr> <relop> <valexpr> | IF <expr> , <expr> , <expr> *)
PROCEDURE expr(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
VAR v1:REAL;
rop:relOpType;
BEGIN
IF nextChar(str, pos) THEN
IF (str[pos] = 'I') AND (str[pos+1] = 'F') THEN
INC(pos, 2);
ifexpr(str, pos, v, s, eval);
ELSE
valexpr(str, pos, v, s, eval);
IF s = OK THEN
IF nextChar(str, pos) THEN
relOp(str, pos, rop, s);
IF s <> OK THEN (* shouldn't have looked at next char *)
s := OK;
ELSE
valexpr(str, pos, v1, s, eval);
IF s = OK THEN
IF applyRelOp(rop, v, v1) THEN
v := 1.0;
ELSE
v := 0.0;
END;
END;
END;
END;
END;
END;
ELSE
s := SyntaxError;
error(str, pos);
END;
END expr;
(* IF <expr> , <expr> , <expr> *)
PROCEDURE ifexpr(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
(* ifexpr has to eval both branches, even though it knows the
value of the test, because we do not separate parsing from evaluation.
It doesn't cause a problem because there are no side-effects. *)
VAR vTrue, vFalse:REAL;
BEGIN
expr(str, pos, v, s, eval);
IF s = OK THEN
IF (NOT nextChar(str, pos)) OR (str[pos] <> ',') THEN
s := SyntaxError;
error(str, pos);
ELSE
INC(pos);
expr(str, pos, vTrue, s, v <> 0.0);
IF s = OK THEN
IF (NOT nextChar(str, pos)) OR (str[pos] <> ',') THEN
s := SyntaxError;
error(str, pos);
ELSE
INC(pos);
expr(str, pos, vFalse, s, v = 0.0);
IF s = OK THEN
IF v = 0.0 THEN
v := vFalse;
ELSE
v := vTrue;
END;
END;
END;
END;
END;
END;
END ifexpr;
PROCEDURE relOp(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR rop:relOpType; VAR s:Status);
BEGIN
IF str[pos] = '=' THEN
rop := Equal;
s := OK;
INC(pos);
ELSIF str[pos] = '>' THEN
IF str[pos+1] = '=' THEN
rop := GreaterEqual;
INC(pos, 2);
s := OK;
ELSE
rop := Greater;
s := OK;
INC(pos);
END;
ELSIF str[pos] = '<' THEN
IF str[pos+1] = '=' THEN
rop := LessEqual;
INC(pos, 2);
s := OK;
ELSE
rop := Less;
INC(pos);
s := OK;
END;
ELSE
s := SyntaxError; (* no message; this isn't a real error *)
END;
END relOp;
PROCEDURE applyRelOp(rop:relOpType; v1, v2:REAL):BOOLEAN;
BEGIN
CASE rop OF
Equal: RETURN v1 = v2;
| NotEqual: RETURN v1 <> v2;
| Less: RETURN v1 < v2;
| Greater: RETURN v1 > v2;
| LessEqual: RETURN v1 <= v2;
| GreaterEqual: RETURN v1 >= v2;
ELSE
fatal('applyBoolOp: unknown op type');
END;
END applyRelOp;
(* <valexpr> ::= <term> { <addop> <term> } *)
PROCEDURE valexpr(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
VAR v1:REAL;
op:CHAR;
BEGIN
term(str, pos, v, s, eval);
WHILE (s = OK) AND nextChar(str, pos) DO
IF NOT addOp(str[pos]) THEN
RETURN;
END;
op := str[pos];
INC(pos);
term(str, pos, v1, s, eval);
IF (s = OK) AND eval THEN
IF op = '+' THEN
v := v + v1;
ELSE
v := v - v1;
END;
END;
END;
END valexpr;
(* <term> ::= <factor>{ <mulop> <factor> } *)
PROCEDURE term(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
VAR v1:REAL;
op:CHAR;
BEGIN
factor(str, pos, v, s, eval);
WHILE (s = OK) AND nextChar(str, pos) DO
IF NOT mulOp(str[pos]) THEN
RETURN;
END;
op := str[pos];
INC(pos);
factor(str, pos, v1, s, eval);
IF (s = OK) AND eval THEN
IF op = '*' THEN
v := v * v1;
ELSIF v1 = 0.0 THEN
s := DivByZero;
ELSE
v := v / v1;
END;
END;
END;
END term;
(* <factor> ::= real | <cellref> | - <factor> | ( <expr> ) *)
PROCEDURE factor(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
BEGIN
IF NOT nextChar(str, pos) THEN
s := SyntaxError;
error(str, pos);
ELSIF isDigit(str[pos]) THEN
parseReal(str, pos, v, s);
ELSE
INC(pos);
CASE str[pos-1] OF
'[': cellRef(str, pos, v, s, eval);
| '-': factor(str, pos, v, s, eval);
v := -v;
| '(': expr(str, pos, v, s, eval);
IF s = OK THEN
IF (NOT nextChar(str, pos)) OR (str[pos] <> ')') THEN
s := SyntaxError;
error(str, pos);
ELSE
INC(pos);
END;
END;
ELSE
s := SyntaxError;
error(str, pos);
END;
END;
END factor;
(* <cellref> ::= [ <refexpr> , <refexpr> ]
Opening [ is already read. *)
PROCEDURE cellRef(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; eval:BOOLEAN);
VAR vRow, vCol:REAL;
r, c:CARDINAL;
BEGIN
refexpr(str, pos, vRow, s, curRow);
IF s = OK THEN
IF (NOT nextChar(str, pos)) OR (str[pos] <> ',') THEN
s := SyntaxError;
error(str, pos);
ELSE
INC(pos);
refexpr(str, pos, vCol, s, curCol);
IF s = OK THEN
IF eval THEN
rangeCheck(vRow, vCol, r, c, s);
END;
IF s = OK THEN
IF eval THEN
reference(r, c, v, s);
END;
IF s = OK THEN
IF nextChar(str, pos) AND (str[pos] = ']') THEN
INC(pos);
ELSE
s := SyntaxError;
error(str, pos);
END;
END;
END;
END;
END;
END;
END cellRef;
(* <refexpr> ::= <addop> real | real *)
PROCEDURE refexpr(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL;
VAR v:REAL; VAR s:Status; addBase:CARDINAL);
VAR op:CHAR;
BEGIN
IF NOT nextChar(str, pos) THEN
s := SyntaxError;
error(str, pos);
ELSE
IF addOp(str[pos]) THEN
op := str[pos];
INC(pos);
ELSE
op := 0C;
END;
IF NOT nextChar(str, pos) THEN
s := SyntaxError;
error(str, pos);
ELSE
parseReal(str, pos, v, s);
IF s = OK THEN
IF op = '+' THEN
v := FLOAT(addBase) + v;
ELSIF op = '-' THEN
v := FLOAT(addBase) - v;
END;
END;
END;
END;
END refexpr;
PROCEDURE addOp(c:CHAR):BOOLEAN;
BEGIN
RETURN (c = '+') OR (c = '-');
END addOp;
PROCEDURE mulOp(c:CHAR):BOOLEAN;
BEGIN
RETURN (c = '*') OR (c = '/');
END mulOp;
PROCEDURE rangeCheck(vRow, vCol:REAL; VAR r, c:CARDINAL; VAR s:Status);
BEGIN
IF (vRow >= 1.0) AND (vRow <= FLOAT(maxRow())) AND
(vCol >= 1.0) AND (vCol <= FLOAT(maxCol())) THEN
s := OK;
r := TRUNC(vRow);
c := TRUNC(vCol);
ELSE
s := RangeError;
END;
END rangeCheck;
PROCEDURE reference(row, col:CARDINAL; VAR v:REAL; VAR s:Status);
BEGIN
IF status(row, col) = OK THEN
v := getValue(row, col);
s := OK;
ELSE
s := RefError;
END;
END reference;
PROCEDURE parseReal(VAR str:ARRAY OF CHAR; VAR pos:CARDINAL; VAR v:REAL;
VAR s:Status);
VAR real,msg:string40;
endPos:CARDINAL;
BEGIN
skipToEndOfReal(str, pos, endPos);
stringCopy(real, str, pos, endPos);
StrToReal(real, v);
CASE RealConversionRes OF
noError: s := OK;
| invalidStr: s := SyntaxError;
Concat(msg, "Invalid real: ", real);
message(msg);
| overflow: s := Overflow;
| underflow: s := Underflow;
ELSE
fatal("parseReal: unknown error");
END;
pos := endPos+1;
END parseReal;
PROCEDURE skipToEndOfReal(str:ARRAY OF CHAR; pos:CARDINAL;VAR endPos:CARDINAL);
BEGIN
endPos := pos;
WHILE (endPos <= HIGH(str)) AND
findChar("0123456789E.", str[endPos], pos) DO
INC(endPos);
END;
DEC(endPos);
END skipToEndOfReal;